package uk.org.landeg.euler.problems;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import uk.org.landeg.euler.problems.framework.AbstractProblem;
import uk.org.landeg.euler.problems.framework.ProblemLauncher;

public class Problem054 extends AbstractProblem{
	static char[] ranks = {'2','3','4','5','6','7','8','9','T','J','Q','K','A'};
	static char[] suits = {'D','S','H','C'};

	public static void main(String[] args) {
		ProblemLauncher launcher = new ProblemLauncher(new Problem054());
		launcher.solve();		
	}

	@Override
	public String getDescription() {
		// TODO Auto-generated method stub
		return "Determine how many hands of poker player 1 wins";
	}

	@Override
	public Integer getProblemNumber() {
		// TODO Auto-generated method stub
		return 54;
	}
	
	@Override
	public Object solve() {
		int p1wins = 0;
		try {
			Pattern handMatch = Pattern.compile("(.. .. .. .. ..) (.. .. .. .. ..)");
			BufferedReader reader = new BufferedReader (new FileReader ("data/poker.txt"));
			String line;
			Matcher m;
			Hand player1 = null,player2 = null;
			Iterator<AbstractWin> p1it,p2it;
			int comp;
			while ((line = reader.readLine()) != null) {
				m = handMatch.matcher(line);
				m.find();
				player1 = new Hand(m.group(1));
				player2 = new Hand(m.group(2));
				checkAndSetWins(player1);
				checkAndSetWins(player2);
				p1it = player1.wins.iterator();
				p2it = player2.wins.iterator();
				while (p1it.hasNext() && p2it.hasNext()) {
					comp = p1it.next().compareTo(p2it.next());
					if (comp < 0) {
						p1wins++;
						break;
					}
					if (comp > 0) {
						break;
					}
				}
				
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return p1wins;
	}
	
	private void checkAndSetWins (Hand hand) {
		new HighCard().test(hand);
		new OnePair().test(hand);
		new TwoPairs().test(hand);
		new ThreeofaKind().test(hand);
		new FullHouse().test(hand);
		new Straight().test(hand);
		new Flush().test(hand);
		new StraightFlush().test(hand);
		new FourofaKind().test(hand);
		new RoyalFlush().test(hand);
	}
	
	class Card implements Comparable<Card>{
		int rank, suit;
		
		public Card(String desc) {
			char test = desc.toCharArray()[0];
			int idx = 0;
			for (char c : ranks) {
				if (c == test) {
					rank = idx;
					break;
				}
				idx++;
			}
			idx = 0;
			
			test = desc.toCharArray()[1];
			for (char c : suits) {
				if (c == test) {
					suit = idx;
					break;
				}
				idx++;
			}
		}

		@Override
		public int compareTo(Card o) {
			return rank - o.rank;
		}
		
		@Override
		public String toString() {
			return new String ("" + ranks[rank] + suits[suit]);
		}
	}
	class Hand {
		Card cards[];
		Set<AbstractWin> wins;
		public Hand(String desc) {
			cards = new Card[5];
			String cDesc[] = desc.split(" ");
			int id= 0;
			for (String c : cDesc) {
				cards[id++] = new Card(c);
			}
			Arrays.sort(cards);
			wins = new TreeSet<AbstractWin>();
		}
		
		@Override
		public String toString() {
			String ret = new String();
			for (Card c : cards) {
				ret += c.toString();
			}
			return ret;
		}
	}
	
	abstract class AbstractWin implements Comparable<AbstractWin>{
		int maxRank;
		protected int rank;
		abstract int getScore ();
		abstract void test (Hand hand);
		
		int getRank () {
			return rank;
		}
		@Override
		public int compareTo(AbstractWin o) {
			int scoreDiff = o.getScore() - getScore();
			if (scoreDiff == 0) {
				return o.getRank() - getRank();
			}
			return scoreDiff;
		}
	}
	
	class HighCard extends AbstractWin {
		@Override
		int getScore() {
			return 1;
		}

		@Override
		void test(Hand hand) {
			for (int idx = 0 ; idx < hand.cards.length ; idx++) {
				HighCard w = new HighCard();
				w.rank = hand.cards[idx].rank;
				hand.wins.add(w);
			}
		}
		
		@Override
		public String toString() {
			return "High Card : " + rank;
		}
		
	}
	
	abstract class CollectionWin extends AbstractWin {
		int[] rankCount (Hand hand) {
			int[] count = new int[ranks.length];
			Arrays.fill(count, 0);
			for (int idx = 0 ; idx < hand.cards.length ; idx++) {
				count[hand.cards[idx].rank]++;
			}
			return count;
		}
	}
	
	class OnePair extends CollectionWin {
		@Override
		int getScore() {
			// TODO Auto-generated method stub
			return 2;
		}
		@Override
		void test(Hand hand) {
			int[] freq = rankCount(hand);
			for (int idx = 0 ; idx < freq.length ; idx++) {
				if (freq[idx] == 2) {
					OnePair w = new OnePair();
					w.rank = idx;
					hand.wins.add(w);
				}
			}
		}
		
		@Override
		public String toString() {
			return "Pair : " + rank;
		}		
	}
	
	class TwoPairs extends CollectionWin {

		@Override
		int getScore() {
			// TODO Auto-generated method stub
			return 3;
		}

		@Override
		void test(Hand hand) {
			int pairCount = 0;
			int bestRank = 0;
			int[] freq = rankCount(hand);
			for (int idx = 0 ; idx < freq.length ; idx++) {
				if (freq[idx] == 2) {
					pairCount++;
					if (pairCount==2) {
						bestRank = idx;
						TwoPairs p = new TwoPairs();
						p.rank = idx;
						hand.wins.add(p);
						break;
					}
				}
			}
		}
		
		@Override
		public String toString() {
			return "Two Pair : " + rank;
		}		
		
	}	
	class ThreeofaKind extends CollectionWin {

		@Override
		public int getScore() {
			// TODO Auto-generated method stub
			return 4;
		}

		@Override
		void test(Hand hand) {
			int[] freq = rankCount(hand);
			for (int idx = 0 ; idx < freq.length ; idx++) {
				if (freq[idx] == 3) {
					ThreeofaKind w = new ThreeofaKind();
					w.rank = idx;
					hand.wins.add(w);
				}
			}
		}
		@Override
		public String toString() {
			return "Three of a kind : " + rank;
		}		
	}
	class Straight extends AbstractWin {

		@Override
		public int getScore() {
			return 5;
		}

		@Override
		public void test(Hand hand) {
			for (int idx = 0 ; idx < hand.cards.length ;idx++) {
				if (hand.cards[1].rank == hand.cards[0].rank + 1 
						&& hand.cards[2].rank == hand.cards[0].rank + 2
						&& hand.cards[3].rank == hand.cards[0].rank + 3
						&& hand.cards[4].rank == hand.cards[0].rank + 4) {
					Straight win = new Straight();
					win.rank = hand.cards[4].rank;
					hand.wins.add(win);
				}
			}
		}
		
		@Override
		public String toString() {
			return "Straight : " + rank;
		}		
	}
	
	class Flush extends AbstractWin {

		@Override
		public int getScore() {
			return 6;
		}

		@Override
		public void test(Hand hand) {
			for (int idx = 0 ; idx < hand.cards.length ;idx++) {
				if (hand.cards[1].suit == hand.cards[0].suit 
						&& hand.cards[2].suit == hand.cards[0].suit
						&& hand.cards[3].suit == hand.cards[0].suit
						&& hand.cards[4].suit == hand.cards[0].suit) {
					Flush win = new Flush();
					win.rank = hand.cards[4].rank;
					hand.wins.add(win);
				}
			}
		}
		
		@Override
		public String toString() {
			return "Flush : " + rank;
		}		
	}
	class FullHouse extends CollectionWin {

		@Override
		public int getScore() {
			return 7;
		}

		@Override
		public void test(Hand hand) {
			boolean pFound = false, tFound = false;
			int rank  =0 ;
			for (AbstractWin win : hand.wins) {
				if (win instanceof OnePair) {
					pFound = true;
				}
				if (win instanceof ThreeofaKind) {
					tFound = true;
					rank = win.rank;
				}
			}
			if (tFound && pFound) {
				FullHouse win = new FullHouse();
				win.rank = rank;
				hand.wins.add(win);
			}
		}
		
		@Override
		public String toString() {
			return "Full House : " + rank;
		}		
	}
	
	class FourofaKind extends CollectionWin {

		@Override
		public int getScore() {
			// TODO Auto-generated method stub
			return 8;
		}

		@Override
		void test(Hand hand) {
			int[] freq = rankCount(hand);
			for (int idx = 0 ; idx < freq.length ; idx++) {
				if (freq[idx] == 4) {
					FourofaKind w = new FourofaKind();
					w.rank = idx;
					hand.wins.add(w);
				}
			}
		}
		@Override
		public String toString() {
			return "Four of a kind: " + rank;
		}
	}
	class StraightFlush extends Flush {
		@Override
		public int getScore() {
			// TODO Auto-generated method stub
			return 9;
		}
		@Override
		public void test(Hand hand) {
			boolean isFlush = false, isStraight = false;
			int rank;
			for (AbstractWin w : hand.wins) {
				if (w instanceof Flush) {
					isFlush = true;
				}
				if (w instanceof Straight) {
					isStraight = true;
					rank = w.rank;
				}
			}
			if (isFlush && isStraight) {
				StraightFlush w = new StraightFlush();
				hand.wins.add(w);
			}
		}
		
		@Override
		public String toString() {
			return "St Flush : " + rank;
		}
	}
	
	class RoyalFlush extends AbstractWin {

		@Override
		public int getScore() {
			// TODO Auto-generated method stub
			return 10;
		}

		@Override
		public void test(Hand hand) {
			for (AbstractWin w : hand.wins) {
				if (w instanceof StraightFlush) {
					RoyalFlush rf = new RoyalFlush();
					rf.rank = w.rank;
					hand.wins.add(rf);
				}
			}
		}
		@Override
		public String toString() {
			return "Royal Flush : " + rank;
		}
	}
	
}
